/* tcp_socket.c - funções auxiliares para uso de sockets TCP
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/types.h>
#include <sys/types.h>
#include <netdb.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>

#include "tcp_socket.h"

extern int errno; /* errno e' uma variavel definida pelo sistema */

int my_transp=0; /* se my_transp!=0, retorna erros como syscall */

#define HANDLE_ERROR(str)    \
    do { if(my_transp) return -1; else {perror(str); exit(1);} } while (0)

/*
 * __________________ passive_socket( local_port, remote_port, remote_host ) 
 *
 * Executa as camadas das funcoes socket, bind e listen para inicializar um
 * socket TCP aberto passivamente para receber conexoes no porto indicado.
 * Em seguida executa um accept e verifica se o interlocutor do outro lado
 * da conexão é quem deveria ser.
 */

int
passive_socket( u_short local_port, u_short remote_port,
                struct hostent* remote_host)
{
  int endps, accepts;
  int yes=1;
  struct sockaddr_in local_addr;
  struct sockaddr_in remote_addr;
  unsigned           addrlen = sizeof(remote_addr);

  if ((accepts=socket(PF_INET,SOCK_STREAM,0))<0) {
      HANDLE_ERROR("passive_socket/socket");
  }

  /* lose the pesky "Address already in use" error message */
  if (setsockopt(accepts,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
      HANDLE_ERROR("setsockopt");
  } 
  local_addr.sin_family = PF_INET;
  local_addr.sin_port   = htons(local_port);
  local_addr.sin_addr.s_addr = INADDR_ANY;
  if (bind(accepts, (struct sockaddr*)&local_addr, sizeof(local_addr))<0) {
      HANDLE_ERROR("passive_socket/bind");
  }
  if (listen(accepts, 4)<0) {
      HANDLE_ERROR("passive_socket/listen");
  }
  /* Agora, entao, e' só receber uma conexao */
  if ((endps=accept(accepts,(struct  sockaddr *)&remote_addr, &addrlen))<0) {
      HANDLE_ERROR("simple_accept/acct");
  }
  if ((htons(remote_port)!=remote_addr.sin_port)
     ||memcmp(&(remote_addr.sin_addr.s_addr),remote_host->h_addr,4)) {
      fprintf(stderr,"lolev: other endpoint(%s:%d) not as expected (%s:%d)\n",
              inet_ntoa(remote_addr.sin_addr),ntohs(remote_addr.sin_port),
              inet_ntoa(*((struct in_addr*)remote_host->h_addr)),remote_port);
      exit(1);
  }


  if (setsockopt(endps,IPPROTO_TCP,TCP_NODELAY,&yes,sizeof(int)) == -1) {
      perror("setsockopt(NODELAY)");
      exit(1);
  } 
  return endps;
}

/*
 * _____________ active_socket( local_port, server_port, remote_host ) ___
 *
 * Cria um socket TCP com um porto qualquer e conecta-o a outro socket na
 * maquina identificada pelo string nome_host, no porto indicado. Em caso
 * de erro exibe uma mensagem e termina o programa. Caso contrario retorna
 * o socket inicializado, com a conexao ja' aberta.
 * O nome da maquina do servidor deve ser fornecido como um string que pode
 * conter o numero IP em notacao decimal com pontos ou o nome propriamente
 * dito. Para isso utiliza-se a funcao gethostbyname.
 */
int
active_socket(u_short local_port, u_short remote_port,
              struct hostent* remote_host)
{
  int s, yes=1;
  struct sockaddr_in remote_addr;
  struct sockaddr_in local_addr;

  if ((s=socket(PF_INET,SOCK_STREAM,0))<0) {
      HANDLE_ERROR("passive_socket/socket");
  }
  if (setsockopt(s,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int)) == -1) {
      HANDLE_ERROR("setsockopt");
  } 
  local_addr.sin_family = PF_INET;
  local_addr.sin_port   = htons(local_port);
  local_addr.sin_addr.s_addr = INADDR_ANY;
  if (bind(s, (struct sockaddr*)&local_addr, sizeof(local_addr))<0) {
      HANDLE_ERROR("passive_socket/bind");
  }
  remote_addr.sin_family      = PF_INET;
  remote_addr.sin_port        = htons(remote_port);
  bcopy(remote_host->h_addr,
          &remote_addr.sin_addr.s_addr,sizeof(remote_addr.sin_addr.s_addr));

  if (connect(s,(struct sockaddr*)&remote_addr, sizeof(remote_addr))<0) {
      HANDLE_ERROR("passive_socket/connect");
  }
  return s;
}

/*
 * ______________________________ socket_read( socket, buf, buflen ) ______
 *
 * Le^ buflen bytes do socket e armazena-os no buffer indicado. Ao contrario
 * do read, so' retorna ao completar o numero de bytes indicado ou em caso de
 * erro.
 */
int socket_read( int s, void* buf, int buflen )
{
  int nread, recvd=0;
  while ((nread=read(s,buf,buflen))>0) {
    buflen-= nread;
    buf   += nread;
    recvd += nread;
  }
  return (recvd>0)? recvd: nread;
}

/*
 * ______________________________ socket_write( socket, buf, buflen ) ______
 *
 * Escreve buflen bytes do buffer para o socket indicado. Ao contrario
 * do write, so' retorna ao completar o numero de bytes indicado ou em caso de
 * erro.
 */
int socket_write( int s, void* buf, int buflen )
{
  int nwritten, sent=0;
  while ((nwritten=write(s,buf,buflen))>0) {
    buflen-= nwritten;
    buf   += nwritten;
    sent  += nwritten;
  }
  return (sent>0)? sent: nwritten;
}
